package ArithmeticObjects;

import java.util.InputMismatchException;
import ArithUtils.*;
import CryptoPrim.PRG;
import FiatShamirProofs.ProofUtils;
import Utils.ByteTree;
import Utils.Pair;

/**
 * @author Shir Peleg
 * This class represents an Elliptic Curve which extends Group of pairs of field elements. 
 */

public class EllipticCurve extends ConvertableGroup<Pair<BTField<BigNumber>.BTElement, BTField<BigNumber>.BTElement>> 
{
	private String name;
	private BTField<BigNumber> field;
	private BTField<BigNumber>.BTElement ACoaf;
	private BTField<BigNumber>.BTElement BCoaf;
	private BTElement generator;

	public EllipticCurve(String name)
	{
		this(name, new EllipticCurveParam(name));
	}
	
	public EllipticCurve(String name, EllipticCurveParam params)
	{
		this(name, params, staticTypes.getNumberField(params.fieldChar));
	}
	
	public EllipticCurve(String name, EllipticCurveParam params, final BTField<BigNumber> field)
	{
		super(UsfulOppStructs.getECMultOp(name), params.groupOrder, UsfulOppStructs.getECtoBT(), UsfulOppStructs.getECfromBT(field));
		this.field = field;
		this.ACoaf = field.getElement(params.aCoaf);
		this.BCoaf = field.getElement(params.bCoaf);
		BTField<BigNumber>.BTElement xCoef = this.field.getElement(params.genXCoaf);
		BTField<BigNumber>.BTElement yCoef = this.field.getElement(params.genYCoaf);
		Pair<BTField<BigNumber>.BTElement, BTField<BigNumber>.BTElement> value = new Pair<BTField<BigNumber>.BTElement, BTField<BigNumber>.BTElement>(xCoef, yCoef);
		this.generator = new BTElement(value);
		this.name = params.name;
	}
	
	public EllipticCurve(ByteTree bt)
	{
		this(bt.getTheNChild(1).getString());
	}
	/**
	 * @param t1
	 * @param t2
	 * @return An element with the value (t1,t2)
	 */
	public BTElement getElement(BigNumber t1, BigNumber t2) {
		return super.getElement(new Pair<BTField<BigNumber>.BTElement, BTField<BigNumber>.BTElement>(field.getElement(t1), field.getElement(t2)));
	}
	/** 
	 * @return A Random Array of  ElipticCurve.Element.
	 */
	@Override
	public Array<BTElement> randomArray(int N, PRG prg, int nr) throws InputMismatchException{
		Array<BTElement> res=new Array<BTElement>();
		BigNumber exp=BigNumber.ONE;
		BigNumber FChar=field.getOrder();
		int np=FChar.bitLength();
		BigNumber [] numbers=ProofUtils.deriveRandomBigNumbers(prg, nr+np, 4*N, exp,FChar);
		BigNumber bigN = new BigNumber(N);
		int i=0;
		BigNumber maxPow2 = field.getOrder().substract(BigNumber.ONE).getMaxTwoExp();
		BigNumber res2 = field.getOrder().divide(BigNumber.TWO.pow(maxPow2));
		BigNumber minZ = getMinimalNQR(field.getOrder());
		while((res.getLength().compareTo(bigN)<0) && (i<numbers.length)) {
		//	BigNumber temp=numbers[i].modulo(BigNumber.TWO.pow(nr+np));
		//	BigNumber ziValue=temp.modulo(FChar);
		//	BTField<BigNumber>.BTElement zi=field.getElement(ziValue);
			BTField<BigNumber>.BTElement zi=field.getElement(numbers[i]);
			if(isQuadraticResedue(getFuncRes(zi)))
			{
				BTField<BigNumber>.BTElement yi=field.getElement(getMinimalRoot(getFuncRes(zi), maxPow2, res2, minZ));
				res.add(new BTElement(new Pair<BTField<BigNumber>.BTElement, BTField<BigNumber>.BTElement>(zi, yi)));
			}
			i++;
		}
		if(res.getLength().compareTo(new BigNumber(N))!=0)
			throw new InputMismatchException("Not Enought Numbers, to generate wanted size array of ElupticCureve.Element");
		return res;
	}
	/**
	 * @return the minimal element of the field Z-groupOrder, which is not a quadratic residue.
	 */
	private BigNumber getMinimalNQR(BigNumber groupOrder) {
		BigNumber i=BigNumber.TWO;
		while(i.compareTo(field.getOrder())<=0)
		{
			BTField<BigNumber>.BTElement elem=field.getElement(i);
			if(!isQuadraticResedue(elem))
				return elem.getValue();
			i=i.add(BigNumber.ONE);
		}
		return null;
	}
	/**
	 * @return the minimal root of Fzi in this.field.
	 */
	private BigNumber getMinimalRoot(BTField<BigNumber>.BTElement Fzi, BigNumber maxPow2, BigNumber resQ, BigNumber minZ) {
		BigNumber res;
		BigNumber[] UnitArr=new BigNumber[10];
		for (int i=0;i<10;i++) {
			UnitArr[i]=new BigNumber(i);
		}
		if (field.getOrder().modulo(UnitArr[4]).compareTo(UnitArr[3])==0)
		{
			res = field.pow(Fzi, (field.getOrder().add(UnitArr[1])).divide(UnitArr[4])).getValue();
		}
		else
		{		
			BigNumber M=maxPow2;

			BTField<BigNumber>.BTElement c=field.pow(field.getElement(minZ), resQ);
			BTField<BigNumber>.BTElement R=field.pow(Fzi, (resQ.add(BigNumber.ONE)).divide(BigNumber.TWO));
			BTField<BigNumber>.BTElement t=field.pow(Fzi, resQ);
			while(!field.ElementIsEqual(t, field.getGenerator()))
			{
				BigNumber i=BigNumber.ZERO;
				BTField<BigNumber>.BTElement temp=t;
				while((!field.ElementIsEqual(temp, field.getGenerator())) && (i.compareTo(M)<0)){
					temp=field.pow(temp, BigNumber.TWO);
					i=i.add(BigNumber.ONE);
				}
				BigNumber exp=M.substract(i.add(BigNumber.ONE));	
				BTField<BigNumber>.BTElement b=field.pow(c, BigNumber.TWO.pow(exp));
				R=field.mult(R, b);
				t=field.mult(t, field.pow(b, BigNumber.TWO));
				c=field.pow(b, BigNumber.TWO);
				M=i;
			}
			res=R.getValue();
		}
		if(res.compareTo(field.getOrder().substract(res))>=0)
		{
			return field.getOrder().substract(res); 
		}
		return res;
	}
	/**
	 * @return an array containing the values of the group's Equation
	 */
	public BigNumber[] getCoaf()
	{
		BigNumber[] arr = {ACoaf.getValue(),BCoaf.getValue()};
		return arr;
	}
	/**
	 * @return The groups generator.
	 */
	public BTElement getGenerator() {
		return this.generator;
	}
	/**
	 * @return true iff elem is Quadratic residue in this.field
	 */
	private boolean isQuadraticResedue(BTField<BigNumber>.BTElement elem)
	{
		BigNumber exp=(field.getOrder().substract(BigNumber.ONE)).divide(BigNumber.TWO);
		return (field.pow(elem, exp).getValue().compareTo(BigNumber.ONE)==0);
	}
	/**
	 * @return F(z)- the curve res over zi.
	 */
	private BTField<BigNumber>.BTElement getFuncRes(BTField<BigNumber>.BTElement elem)
	{
		return field.add(field.add((field.pow(elem,new BigNumber("3"))),field.mult(elem, ACoaf)),BCoaf);
	}
	/**
	 * @return the ByteTree representation of the group
	 */
	@Override
	public ByteTree getByteTreeRep(){
		ByteTree bt = ByteTree.generateEmptyNode();
		bt.addChild(ByteTree.generateNewLeaf("verificatum.arithm.ECqPGroup"));
		bt.addChild(ByteTree.generateNewLeaf(this.name));
		return bt;
	}
	/**
	 * @param a
	 * @param b
	 * @return true iff a=b.
	 */
	@Override
	public boolean ElementIsEqual(BTElement a, BTElement b){
		boolean fc = (a.getValue().getT1().getValue().compareTo(b.getValue().getT1().getValue()) == 0);
		boolean sc = (a.getValue().getT2().getValue().compareTo(b.getValue().getT2().getValue()) == 0);
		return fc && sc;
				
	}


}
